﻿using System;
using System.Diagnostics;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using AsyncWorkloadHelper;
using Microsoft.Phone.Shell;

namespace ShellUiHelpers
{
  /// <summary>
  /// Helper for updating tiles
  /// </summary>
  public static class TileHelper
  {
    static IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();

    /// <summary>
    /// Empty URI used to remove a property from a tile update
    /// </summary>
    static readonly Uri EMPTY_URI = new Uri("", UriKind.Relative);

    /// <summary>
    /// The root of the shared image folder in IsoStore
    /// </summary>
    const string SHARED_IMAGE_PATH = "/shared/shellcontent/";

    /// <summary>
    /// Standard JPEG extension
    /// </summary>
    const string IMAGE_EXTENSION = ".jpg";

    /// <summary>
    /// Scheme used to specify items in IsoStore
    /// </summary>
    const string ISOSTORE_SCHEME = "isostore:";

    /// <summary>
    /// Zero-Width-Space; used to escape '@' sign in content
    /// </summary>
    const char ZWSP = (char)8203;

    /// <summary>
    /// Standard tile height
    /// </summary>
    const int TILE_HEIGHT = 173;

    /// <summary>
    /// Standard tile width
    /// </summary>
    const int TILE_WIDTH = 173;

    /// <summary>
    /// Default to 100% JPEG quality; lowering this number makes the tile look worse and has
    /// no appreciable difference on runtime resource usage
    /// </summary>
    const int JPEG_QUALITY = 100;

    /// <summary>
    /// Standard opacity for the front-of-tile image
    /// </summary>
    const double FRONT_IMAGE_OPACITY = 0.7;

    /// <summary>
    /// Standard opacity for the back-of-tile image
    /// </summary>
    const double BACK_IMAGE_OPACITY = 1.0;

    static bool onWork = false;
    static int secondwork = 0;

    /// <summary>
    /// Updates the primary tile on a background thread
    /// </summary>
    /// <param name="tileData">Tile data to use in the update</param>
    public static void UpdateTileAsync(ExtendedTileData tileData)
    {
      ThreadPool.QueueUserWorkItem(delegate
      {
        UpdateTile(tileData,false,0);
      });
    }

    /// <summary>
    /// Updates the primary tile with a complete set of new information, including compositing text into the
    /// front of the tile and changing the opacity of the image on the back of the tile
    /// </summary>
    /// <param name="tileData">Tile data to use in the update</param>
    /// <remarks>Must be called on a non-UI thread; use the Async version to make this easier</remarks>
    public static void UpdateTile(ExtendedTileData tileData, bool work,int sec)
    {
      if (Deployment.Current.Dispatcher.CheckAccess())
        throw new UnauthorizedAccessException("UpdateTile must be called on a non-UI thread");

      onWork = work;
      secondwork = sec;

      // Asynchronously generate the bitmap images (front and back)
      var workManager = new AsyncWorkManager<ExtendedTileData, string>();
      var backgroundImageWorker = workManager.AddWorkItem(GetBackgroundImage, tileData);

      // Wait for the async operations to complete
      workManager.WaitAll();

      // Get the first (default) tile and create the update data
      ShellTile tile = ShellTile.ActiveTiles.First();
      StandardTileData data = new StandardTileData();

      // Set the front of the tile
      data.Title = tileData.FrontTitle;
      if (backgroundImageWorker.Error == null)
        data.BackgroundImage = new Uri(ISOSTORE_SCHEME + backgroundImageWorker.Result);
      else
        data.BackgroundImage = EMPTY_URI;

      // Update the tile!
      tile.Update(data);
    }

    /// <summary>
    /// Performs a basic tile update for the specified tile without any image compositing, etc.
    /// </summary>
    /// <param name="tileUriSearchTerm">Text to search for in the target tile's URI. The first tile
    /// with this text in the full Uri will be updated</param>
    /// <param name="frontTitle">The title to display on the front of the tile</param>
    /// <param name="backTitle">The title to display on the back of the tile</param>
    /// <param name="backText">The text to display on the back of the tile</param>
    /// <remarks>To leave a property unchanged, pass in null. To remove an existing property, pass in an empty string.
    /// This method can be called on the UI thread since it doesn't need to block</remarks>
    public static void UpdateTile(string tileUriSearchTerm, string frontTitle, string backTitle, string backText)
    {
      // Look for a tile with the search term somewhere in the URI
      ShellTile tile = ShellTile.ActiveTiles.First();
      if (tile == null)
        return;

      // Update the tile if it existed
      StandardTileData tileData = new StandardTileData();
      tileData.Title = frontTitle;

      tile.Update(tileData);
    }

    /// <summary>
    /// Asynchronously Gets the background image for a specific tweet
    /// </summary>
    /// <param name="tweet">The tweet to get the image for</param>
    /// <param name="token">The completion token</param>
    static void GetBackgroundImage(ExtendedTileData tileData, WorkloadInfo<ExtendedTileData, string> token)
    {
      // Must run on the dispatcher thread (due to use of BitmapImage)
      Deployment.Current.Dispatcher.BeginInvoke(delegate
      {
          double opacity = 1.0;

        try
        {
          BitmapImage bi = new BitmapImage();
          bi.CreateOptions = BitmapCreateOptions.None;

          // Pull the standard background image out of our app package
          //bi.SetSource(Application.GetResourceStream(new Uri("background.png", UriKind.Relative)).Stream);

          if (onWork == false)
          {
              // Pull the standard background image out of our app package
              bi.SetSource(Application.GetResourceStream(new Uri("home.png", UriKind.Relative)).Stream);
          }
          else
          {
              // Pull the standard background image out of our app package
              bi.SetSource(Application.GetResourceStream(new Uri("work.png", UriKind.Relative)).Stream);

              opacity = (1.0 - secondwork / 43200.0);
              if (opacity <= 0.0) opacity = 0.0;
          }

          // Compose the image and write to disk
          string fileName = ComposeTileAndWriteToDisk(bi, opacity, tileData.FrontText, "frontimage");

          // Complete the async operation
          token.NotifySuccess(fileName);
        }
        catch (Exception ex)
        {
          token.NotifyFailure(ex);
        }
      });
    }

    /// <summary>
    /// Asynchronously gets the back background image for a specific tweet
    /// </summary>
    /// <param name="tweet">The tweet to get the image for</param>
    /// <param name="token">The completion token</param>
    static void GetBackBackgroundImage(ExtendedTileData tileData, WorkloadInfo<ExtendedTileData, string> token)
    {
        // Must run on the dispatcher thread (due to use of BitmapImage)
        Deployment.Current.Dispatcher.BeginInvoke(delegate
        {
            double opacity = 1.0;

            try
            {
                BitmapImage bi = new BitmapImage();
                bi.CreateOptions = BitmapCreateOptions.None;

                if (onWork == false)
                {
                    // Pull the standard background image out of our app package
                    bi.SetSource(Application.GetResourceStream(new Uri("home.png", UriKind.Relative)).Stream);
                }
                else
                {
                    // Pull the standard background image out of our app package
                    bi.SetSource(Application.GetResourceStream(new Uri("work.png", UriKind.Relative)).Stream);

                    opacity = (1.0 - secondwork/43200.0);

                    if (opacity <= 0.0) opacity = 0.0;
                }

                // Compose the image and write to disk
                string fileName = ComposeTileAndWriteToDisk(bi, opacity, null, "backimage");

                // Complete the async operation
                token.NotifySuccess(fileName);
            }
            catch (Exception ex)
            {
                token.NotifyFailure(ex);
            }
        });
    }

    /// <summary>
    /// Completes the GetBackBackgroundImage async implementation 
    /// </summary>
    /// <param name="tweet">The tweet to get the image for</param>
    /// <param name="image">The downloaded image, or null if it failed</param>
    /// <param name="token">The completion token</param>
    static void CompleteGetBackBackgroundImage(ExtendedTileData tileData, BitmapImage image, WorkloadInfo<ExtendedTileData, string> token)
    {
      // Compose the image and write to disk
      string fileName = ComposeTileAndWriteToDisk(image, BACK_IMAGE_OPACITY, null, "backimage");

      // Complete the async operation
      token.NotifySuccess(fileName);
    }

    /// <summary>
    /// Composes a simple tile image and writes it to disk
    /// </summary>
    /// <param name="background">The background image</param>
    /// <param name="opacity">Desired opacity of the background image</param>
    /// <param name="text">Optional text to render into the image</param>
    /// <param name="baseFileName">The base filename to use (no directory or extension)</param>
    /// <returns>The actual full path to the file as written to disk</returns>
    static string ComposeTileAndWriteToDisk(BitmapImage background, double opacity, string text, string baseFileName)
    {
      // Create a container of the correct size
      Grid container = new Grid();
      container.Width = TILE_WIDTH;
      container.Height = TILE_HEIGHT;

      SolidColorBrush brush;
      if (opacity <= 0.3)
      {
          brush = new SolidColorBrush(Colors.White);
      }
      else
      {
          brush = new SolidColorBrush(Colors.Black);
      }

      // Add the text, if necessary
      if (String.IsNullOrWhiteSpace(text) != true)
      {
        container.Children.Add(new TextBlock
        {
          Text = text,
          TextWrapping = TextWrapping.Wrap,
          Foreground = brush,
          // The font size, line height, and margin have all been chosen to match the shell's rendering of text on tiles
          FontSize = 30,
          LineHeight = 32,
          LineStackingStrategy = LineStackingStrategy.BlockLineHeight,
          Margin = new Thickness(12, 10, 8, 34)
        });
      }

      // Add the background
      container.Background = new ImageBrush
      {
        ImageSource = background,
        Opacity = opacity,
      };

      // Force the container to render itself
      container.Arrange(new Rect(0, 0, TILE_WIDTH, TILE_HEIGHT));

      // Write the image to disk and return the filename
      return WriteShellTileUIElementToDisk(container, baseFileName);
    }

    /// <summary>
    ///  Writes a UI element to disk in the appropriate format for a shell tile
    /// </summary>
    /// <param name="element">The UIElement to write to disk</param>
    /// <param name="baseFileName">The base filename to use (no path or extension)</param>
    /// <returns>The full name of the file, including the directory and the JPG extension</returns>
    static string WriteShellTileUIElementToDisk(UIElement element, string baseFileName)
    {
      WriteableBitmap wb = new WriteableBitmap(element, null);

      // All content must be in this sub-folder of IsoStore
      string fileName = SHARED_IMAGE_PATH + baseFileName + IMAGE_EXTENSION;
      IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, System.IO.FileMode.Create, isf);

      // Write the JPEG using the standard tile size
      // Sometimes the bitmap has (0,0) size and this fails for unknown reasons with an argument exception
      if (wb.PixelHeight > 0)
        wb.SaveJpeg(stream, wb.PixelWidth, wb.PixelHeight, 0, JPEG_QUALITY);
      else
        Debug.WriteLine("Can't write out file because bitmap had 0,0 size; not sure why");

      stream.Close();

      // Return the filename
      return fileName;
    }
  }
}
